/* AlloyFinger v0.1.15
 * By dntzhang
 * Github: https://github.com/AlloyTeam/AlloyFinger
 */
// 获取两点距离
function getLen(v) {
    return Math.sqrt(v.x * v.x + v.y * v.y)
}

function dot(v1, v2) {
    return v1.x * v2.x + v1.y * v2.y
}

// 获取角度
function getAngle(v1, v2) {
    var mr = getLen(v1) * getLen(v2)
    if (mr === 0) return 0
    var r = dot(v1, v2) / mr
    if (r > 1) r = 1
    return Math.acos(r)
}

function cross(v1, v2) {
    return v1.x * v2.y - v2.x * v1.y
}

function getRotateAngle(v1, v2) {
    var angle = getAngle(v1, v2)
    if (cross(v1, v2) > 0) {
        angle *= -1
    }
    return (angle * 180) / Math.PI
}

var HandlerAdmin = function (el) {
    this.handlers = []
    this.el = el
}

HandlerAdmin.prototype.add = function (handler) {
    this.handlers.push(handler)
}

HandlerAdmin.prototype.del = function (handler) {
    if (!handler) this.handlers = []

    for (var i = this.handlers.length; i >= 0; i--) {
        if (this.handlers[i] === handler) {
            this.handlers.splice(i, 1)
        }
    }
}

HandlerAdmin.prototype.dispatch = function () {
    for (var i = 0, len = this.handlers.length; i < len; i++) {
        var handler = this.handlers[i]
        if (typeof handler === 'function') handler.apply(this.el, arguments)
    }
}

function wrapFunc(el, handler) {
    var handlerAdmin = new HandlerAdmin(el)
    handlerAdmin.add(handler)

    return handlerAdmin
}

var AlloyFinger = function (el, option) {
    this.element = typeof el == 'string' ? document.querySelector(el) : el

    this.start = this.start.bind(this)
    this.move = this.move.bind(this)
    this.end = this.end.bind(this)
    this.cancel = this.cancel.bind(this)
    this.element.addEventListener('touchstart', this.start, false)
    this.element.addEventListener('touchmove', this.move, false)
    this.element.addEventListener('touchend', this.end, false)
    this.element.addEventListener('touchcancel', this.cancel, false)

    this.preV = {
        x: null,
        y: null
    }
    this.pinchStartLen = null
    this.zoom = 1
    this.isDoubleTap = false

    var noop = function () {}

    this.rotate = wrapFunc(this.element, option.rotate || noop)
    this.touchStart = wrapFunc(this.element, option.touchStart || noop)
    this.multipointStart = wrapFunc(this.element, option.multipointStart || noop)
    this.multipointEnd = wrapFunc(this.element, option.multipointEnd || noop)
    this.pinch = wrapFunc(this.element, option.pinch || noop)
    this.swipe = wrapFunc(this.element, option.swipe || noop)
    this.tap = wrapFunc(this.element, option.tap || noop)
    this.doubleTap = wrapFunc(this.element, option.doubleTap || noop)
    this.longTap = wrapFunc(this.element, option.longTap || noop)
    this.singleTap = wrapFunc(this.element, option.singleTap || noop)
    this.pressMove = wrapFunc(this.element, option.pressMove || noop)
    this.twoFingerPressMove = wrapFunc(this.element, option.twoFingerPressMove || noop)
    this.touchMove = wrapFunc(this.element, option.touchMove || noop)
    this.touchEnd = wrapFunc(this.element, option.touchEnd || noop)
    this.touchCancel = wrapFunc(this.element, option.touchCancel || noop)

    this._cancelAllHandler = this.cancelAll.bind(this)

    window.addEventListener('scroll', this._cancelAllHandler)

    this.delta = null
    this.last = null
    this.now = null
    this.tapTimeout = null
    this.singleTapTimeout = null
    this.longTapTimeout = null
    this.swipeTimeout = null
    this.x1 = this.x2 = this.y1 = this.y2 = null
    this.preTapPosition = {
        x: null,
        y: null
    }
}

AlloyFinger.prototype = {
    start: function (evt) {
        if (!evt.touches) return
        this.now = Date.now()
        this.x1 = evt.touches[0].pageX
        this.y1 = evt.touches[0].pageY
        this.delta = this.now - (this.last || this.now)
        this.touchStart.dispatch(evt, this.element)
        if (this.preTapPosition.x !== null) {
            this.isDoubleTap =
                this.delta > 0 &&
                this.delta <= 250 &&
                Math.abs(this.preTapPosition.x - this.x1) < 30 &&
                Math.abs(this.preTapPosition.y - this.y1) < 30
            if (this.isDoubleTap) clearTimeout(this.singleTapTimeout)
        }
        this.preTapPosition.x = this.x1
        this.preTapPosition.y = this.y1
        this.last = this.now
        var preV = this.preV,
            len = evt.touches.length
        if (len > 1) {
            this._cancelLongTap()
            this._cancelSingleTap()
            var v = {
                x: evt.touches[1].pageX - this.x1,
                y: evt.touches[1].pageY - this.y1
            }
            preV.x = v.x
            preV.y = v.y
            this.pinchStartLen = getLen(preV)
            this.multipointStart.dispatch(evt, this.element)
        }
        this._preventTap = false
        this.longTapTimeout = setTimeout(
            function () {
                this.longTap.dispatch(evt, this.element)
                this._preventTap = true
            }.bind(this),
            750
        )
    },
    move: function (evt) {
        if (!evt.touches) return
        var preV = this.preV,
            len = evt.touches.length,
            currentX = evt.touches[0].pageX,
            currentY = evt.touches[0].pageY
        this.isDoubleTap = false
        if (len > 1) {
            var sCurrentX = evt.touches[1].pageX,
                sCurrentY = evt.touches[1].pageY
            var v = {
                x: evt.touches[1].pageX - currentX,
                y: evt.touches[1].pageY - currentY
            }

            if (preV.x !== null) {
                if (this.pinchStartLen > 0) {
                    evt.zoom = getLen(v) / this.pinchStartLen
                    this.pinch.dispatch(evt, this.element)
                }

                evt.angle = getRotateAngle(v, preV)
                this.rotate.dispatch(evt, this.element)
            }
            preV.x = v.x
            preV.y = v.y

            if (this.x2 !== null && this.sx2 !== null) {
                evt.deltaX = (currentX - this.x2 + sCurrentX - this.sx2) / 2
                evt.deltaY = (currentY - this.y2 + sCurrentY - this.sy2) / 2
            } else {
                evt.deltaX = 0
                evt.deltaY = 0
            }
            this.twoFingerPressMove.dispatch(evt, this.element)

            this.sx2 = sCurrentX
            this.sy2 = sCurrentY
        } else {
            if (this.x2 !== null) {
                evt.deltaX = currentX - this.x2
                evt.deltaY = currentY - this.y2

                //move事件中添加对当前触摸点到初始触摸点的判断，
                //如果曾经大于过某个距离(比如10),就认为是移动到某个地方又移回来，应该不再触发tap事件才对。
                var movedX = Math.abs(this.x1 - this.x2),
                    movedY = Math.abs(this.y1 - this.y2)

                if (movedX > 10 || movedY > 10) {
                    this._preventTap = true
                }
            } else {
                evt.deltaX = 0
                evt.deltaY = 0
            }

            this.pressMove.dispatch(evt, this.element)
        }

        this.touchMove.dispatch(evt, this.element)

        this._cancelLongTap()
        this.x2 = currentX
        this.y2 = currentY

        if (len > 1) {
            evt.preventDefault()
        }
    },
    end: function (evt) {
        if (!evt.changedTouches) return
        this._cancelLongTap()
        var self = this
        if (evt.touches.length < 2) {
            this.multipointEnd.dispatch(evt, this.element)
            this.sx2 = this.sy2 = null
        }

        //swipe
        if (
            (this.x2 && Math.abs(this.x1 - this.x2) > 30) ||
            (this.y2 && Math.abs(this.y1 - this.y2) > 30)
        ) {
            evt.direction = this._swipeDirection(this.x1, this.x2, this.y1, this.y2)
            this.swipeTimeout = setTimeout(function () {
                self.swipe.dispatch(evt, self.element)
            }, 0)
        } else {
            this.tapTimeout = setTimeout(function () {
                if (!self._preventTap) {
                    self.tap.dispatch(evt, self.element)
                }
                // trigger double tap immediately
                if (self.isDoubleTap) {
                    self.doubleTap.dispatch(evt, self.element)
                    self.isDoubleTap = false
                }
            }, 0)

            if (!self.isDoubleTap) {
                self.singleTapTimeout = setTimeout(function () {
                    self.singleTap.dispatch(evt, self.element)
                }, 250)
            }
        }

        this.touchEnd.dispatch(evt, this.element)

        this.preV.x = 0
        this.preV.y = 0
        this.zoom = 1
        this.pinchStartLen = null
        this.x1 = this.x2 = this.y1 = this.y2 = null
    },
    cancelAll: function () {
        this._preventTap = true
        clearTimeout(this.singleTapTimeout)
        clearTimeout(this.tapTimeout)
        clearTimeout(this.longTapTimeout)
        clearTimeout(this.swipeTimeout)
    },
    cancel: function (evt) {
        this.cancelAll()
        this.touchCancel.dispatch(evt, this.element)
    },
    _cancelLongTap: function () {
        clearTimeout(this.longTapTimeout)
    },
    _cancelSingleTap: function () {
        clearTimeout(this.singleTapTimeout)
    },
    _swipeDirection: function (x1, x2, y1, y2) {
        return Math.abs(x1 - x2) >= Math.abs(y1 - y2)
            ? x1 - x2 > 0
                ? 'Left'
                : 'Right'
            : y1 - y2 > 0
              ? 'Up'
              : 'Down'
    },

    on: function (evt, handler) {
        if (this[evt]) {
            this[evt].add(handler)
        }
    },

    off: function (evt, handler) {
        if (this[evt]) {
            this[evt].del(handler)
        }
    },

    destroy: function () {
        if (this.singleTapTimeout) clearTimeout(this.singleTapTimeout)
        if (this.tapTimeout) clearTimeout(this.tapTimeout)
        if (this.longTapTimeout) clearTimeout(this.longTapTimeout)
        if (this.swipeTimeout) clearTimeout(this.swipeTimeout)

        this.element.removeEventListener('touchstart', this.start)
        this.element.removeEventListener('touchmove', this.move)
        this.element.removeEventListener('touchend', this.end)
        this.element.removeEventListener('touchcancel', this.cancel)

        this.rotate.del()
        this.touchStart.del()
        this.multipointStart.del()
        this.multipointEnd.del()
        this.pinch.del()
        this.swipe.del()
        this.tap.del()
        this.doubleTap.del()
        this.longTap.del()
        this.singleTap.del()
        this.pressMove.del()
        this.twoFingerPressMove.del()
        this.touchMove.del()
        this.touchEnd.del()
        this.touchCancel.del()

        this.preV =
            this.pinchStartLen =
            this.zoom =
            this.isDoubleTap =
            this.delta =
            this.last =
            this.now =
            this.tapTimeout =
            this.singleTapTimeout =
            this.longTapTimeout =
            this.swipeTimeout =
            this.x1 =
            this.x2 =
            this.y1 =
            this.y2 =
            this.preTapPosition =
            this.rotate =
            this.touchStart =
            this.multipointStart =
            this.multipointEnd =
            this.pinch =
            this.swipe =
            this.tap =
            this.doubleTap =
            this.longTap =
            this.singleTap =
            this.pressMove =
            this.touchMove =
            this.touchEnd =
            this.touchCancel =
            this.twoFingerPressMove =
                null

        window.removeEventListener('scroll', this._cancelAllHandler)
        return null
    }
}

export default AlloyFinger
